package algomission2;

import java.util.Stack;
import java.util.EmptyStackException;

/**
 * Classe se chargeant de la conversion de l'expression a deriver, recue
 * sous forme de String. L'expression est transformee en une instance de
 * la classe LinkedRBinaryTree
 * @author Ballarini Robin
 * @version 15/10/2013
 */
public class Parser{
	
	/** Pile des valeurs (int et x)*/
	private static Stack<LinkedRBinaryTree<?>> valueStack;
	
	/** Pile des operateurs*/
	private static Stack<LinkedRBinaryTree<?>> opStack;
	
	/** Compteur de signes negatifs ~*/
	private static int negative=0;
	
	/**
	 * Attache un operateur a son ou ses operandes
	 * @throws EmptyStackException si une requete pop a ete faite sur une pile vide
	 */
	private static void compute()throws EmptyStackException{
		LinkedRBinaryTree<?> op=opStack.pop();
		LinkedRBinaryTree<?> right=valueStack.pop();
		LinkedRBinaryTree<?> left=null;
		
		// Ni cos, ni sin
		if(!(op.root().element().equals("sin") || op.root().element().equals("cos"))){
			left=valueStack.pop();
			op.setLeft(left);
			op.setRight(right);
		}
		else{ // sin ou cos
			op.setLeft(right);
		}
		
		valueStack.push(op);
		
	}
	
	/**
	 * @pre /
	 * @post Met l'operateur sinus sur la pile des operateurs
	 * 
	 */
	private static void sinOp(){
		opStack.push(new LinkedRBinaryTree<String>(new Node<String>("sin")));
	}
	
	/**
	 * @pre /
	 * @post Met l'operateur cosinus sur la pile des operateurs
	 * 
	 */
	private static void cosOp(){
		opStack.push(new LinkedRBinaryTree<String>(new Node<String>("cos")));
	}
	
	/**
	 * @pre /
	 * @post Incremente le compteur de negativite
	 * 
	 */
	private static void neg(){
		negative++;
	}
	
	/**
	 * @pre o est non null
	 * @post Place l'operateur passe en argument sur la pile
	 * 
	 */
	private static void parseOp(char o){
		opStack.push(new LinkedRBinaryTree<Character>(new Node<Character>(o)));
	}
	
	/**
	 * @pre s!=null && i>=0 && i<s.length()
	 * @post place l'entier en tete de s dans la pile des valeurs et
	 *       renvoie le nouvel indice de lecture sur le caractere qui
	 * 		 est sur le dernier chiffre 
	 * 		(ou -1 si c'est la fin de la chaine)
	 */
	private static int parseInt(String s,int i){
		String sub=s.substring(i);
		char current=sub.charAt(0);
		String num=String.valueOf(current);
		
		/* on parcourt la sous-chaine jusqu'a sortir de celle-ci ou de
		   l'entier, lequel des 2 se produit en premier*/
		int j=1;
		for(j=1;j<sub.length() && Character.isDigit(current);){
			current=sub.charAt(j);
			if(Character.isDigit(current)){
				num=num+current;
				j++;
			}
		}
		
		// fin de la chaine ?
		boolean end=(j>=sub.length());
		
		// On ajoute l'entier a la pile en simplifiant les signes moins
		int a=Integer.parseInt(num);
		if(negative%2!=0){
			valueStack.push(new LinkedRBinaryTree<Integer>(new Node<Integer>(-a)));
		}
		else{
			valueStack.push(new LinkedRBinaryTree<Integer>(new Node<Integer>(a)));
		}
		negative=0;
		
		if(end){
			return -1;
		}
		else{
			return i+j-1;
		}
	}
	
	/**
	 * @pre /
	 * @post Met la variable x sur la pile des valeurs
	 * 
	 */
	private static void parseX(){
		valueStack.push(new LinkedRBinaryTree<Character>(new Node<Character>('x')));
	}
	
	/**
	 * @pre s est non nul et non vide
	 * @post renvoie l'expression passe en argument sous forme d'arbre
	 * 
	 * @throws EmptyStackException illegalite de l'expression
	 * @throws IllegalInputException illegalite de l'expression
	 */
	public static LinkedRBinaryTree<?> parseString(String s) throws EmptyStackException, IllegalInputException{
		
		// initialisation des piles
		opStack=new Stack<LinkedRBinaryTree<?>>();
		valueStack=new Stack<LinkedRBinaryTree<?>>();
		
		// caractère courant
		char current;
		
		// Condition de boucle
		boolean loop=true;
		
		for(int i=0;i<s.length() && loop;i++){
			current=s.charAt(i);
			
			// Operateurs arithmetiques
			if(current=='+' || current=='-' || current=='*' || current=='/' || current=='^'){
				parseOp(current);
			}
			else if(current=='s' || current=='c'){ // sin ou cos
				String r=s.substring(i,i+3);
				if(r.equals("cos")){ // cosinus
					cosOp();
				}
				else if(r.equals("sin")){ // sinus
					sinOp();
				}
				else{ // chaine commancant par c ou s mais illegale
					throw new IllegalInputException("Operateur inconnu commencant par "+current);
				}
				i=i+2;
			}
			else if(current=='x'){ // variable analytique
				parseX();
			}
			else if(Character.isDigit(current)){ // nombre
				i=parseInt(s,i);
				if(i<0){
					loop=false;
				}
			}
			else if(current=='('){
				/* On ne fait rien dans le cas de la parenthese entrante*/
			}
			else if(current==')'){ // parenthese fermante
				compute();
			}
			else if(current=='~'){ // signe de negation (different de '-')
				neg();
			}
			else{
				throw new IllegalInputException("Caractere illegal");
			}
		}
		
		// vérification sur les piles
		boolean valid=valueStack.size()==1 && opStack.size()==0;
		if(!valid){
			throw new IllegalInputException("Cette chaine n'a pas pu etre transformee en arbre");
		}
		
		return valueStack.pop();
	}
}
